package kibahed.uni.setcomperator;

import java.awt.Font;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

import kibahed.uni.charts.AKChart;
import kibahed.uni.charts.ComplexChart;
import kibahed.uni.charts.XYDataPair;
import kibahed.uni.fileinteractions.FileInteractions;
import kibahed.uni.fileinteractions.XMLParser;

@XmlRootElement
public class HashSet_TreeSet_Comperator {

	// use logarithmic scale to show logarithmic growth of the treeSet time;
	// number of cycles??
	// variance gets smaller with 1/squrt(n)
	// n ... number of repetitions

	private final static int MAXIMUM_SIZE = 100000;
	private final static int STEP_SIZE = 10000;
	private final static int CYCLES = 10;
	private final static int STRING_LENGTH = 10;

	private ArrayList<HashSet<Integer>> integerHashSets = new ArrayList<HashSet<Integer>>();
	private ArrayList<TreeSet<Integer>> integerTreeSets = new ArrayList<TreeSet<Integer>>();
	private ArrayList<HashSet<String>> stringHashSets = new ArrayList<HashSet<String>>();
	private ArrayList<TreeSet<String>> stringTreeSets = new ArrayList<TreeSet<String>>();

	private int[] randInts;
	private String[] randomsStrings;

	@XmlElementWrapper
	@XmlAnyElement(lax = true)
	public LinkedList<TestOperation> testOperations = new LinkedList<TestOperation>();

	private LinkedList<AKChart> charts = new LinkedList<AKChart>();

	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException | InstantiationException
				| IllegalAccessException | UnsupportedLookAndFeelException e) {
			e.printStackTrace();
		}
		HashSet_TreeSet_Comperator ht_comp = new HashSet_TreeSet_Comperator();
		ht_comp.setBounds(STEP_SIZE, MAXIMUM_SIZE);
		ht_comp.createRandomIntegers(2 * MAXIMUM_SIZE);
		ht_comp.createRandomStrings(2 * MAXIMUM_SIZE, STRING_LENGTH);

		ht_comp.doTests();

	}

	private void doTests() {

		// setBounds(STEP_SIZE, MAXIMUM_SIZE);

		for (int i = 0; i < 1; i++) {
			testAll_Add();
			testAll_Remove();
			test_All_Contains(true);
			test_All_Contains(false);
		}

		generatCharts();

		File file = FileInteractions.openSaveDiaolg();
		if (file != null)
			XMLParser.saveObject(file, this);
	}

	private void generatCharts() {

		charts = new LinkedList<AKChart>();
		for (TestOperation testOperation : testOperations) {
			for (TestType testType : testOperation.testTypes) {

				AKChart chart = createChartForTestType(testType);
				chart.setTitle(testOperation.testedOperation + "-"
						+ testType.storedDataType);
				charts.add(chart);
			}
		}

		setFonts();

		for (AKChart chart : charts) {

			chart.generateChart(AKChart.panelView);
		}
	}

	private AKChart createChartForTestType(TestType testType) {

		ComplexChart chart = new ComplexChart(AKChart.onlyDataPoints);
		
		prepareChart(chart, testType);

		String[] vn = new String[testType.testClasses.size()];
		String[] fn = new String[vn.length];

		String v = "";
		int i = 0;
		for (TestClass testClass : testType.testClasses) {

			String h = v;
			v += " " + testClass.testedClass;
			vn[i] = v;
			fn[i] = v;
			v = h;
			for (Entry e : testClass.entries) {
				XYDataPair xy = new XYDataPair();
				xy.setxValue(e.amount);
				xy.setyValue(e.timeInNanos);
				chart.addXYDataPair(xy);
			}

			i++;
			chart.switchToNextDataPairList();
		}

		chart.setValueNames(vn);
		chart.setFittedValueNames(fn);
		return chart;
	}

	// TODO think about path and fileName
	private void prepareChart(AKChart chart, TestType testType) {

		chart.setChartType(AKChart.avgPathLengthChart);
		chart.setPath("");
		chart.setTitle(testType.storedDataType + "_"
				+ testType.testClasses.get(0).testedClass);
		chart.setFileName("test");
		chart.setxAxisName("# Elemente");
		chart.setyAxisName("Zeit [ns]");

	}

	private void setFonts() {
		Font titleFont = new Font(Font.SERIF, Font.PLAIN, 50);
		Font valueFont = new Font(Font.SERIF, Font.PLAIN, 40);
		Font labelFont = new Font(Font.SERIF, Font.PLAIN, 60);
		Font legendFont = new Font(Font.SERIF, Font.PLAIN, 40);

		for (AKChart c : charts) {
			c.setTitleFont(titleFont);
			c.setxAxisLabelFont(labelFont);
			c.setyAxisLabelFont(labelFont);
			c.setxAxisValueFont(valueFont);
			c.setyAxisValueFont(valueFont);
			c.setLegendFont(legendFont);
		}
	}

	private void showInConsole() {
		for (TestOperation to : testOperations) {
			StringBuilder sb = new StringBuilder();
			// sb.append("Class: ").append(to.testedClass).append("Type: ").append(to.storedDataType).append("Operation: ").append(tc.testedOperation);
			// for(Entry entry:to.testedOperation.entries){
			// sb.append("\n").append(entry.amount).append("\t").append(entry.timeInNanos);
			// }
			// System.out.println(sb);
		}
	}

	private void testAll_Add() {
		TestOperation testOperation = new TestOperation();
		testOperation.testedOperation = "add";
		testOperations.add(testOperation);

		TestType testTypeInteger = new TestType();
		testTypeInteger.storedDataType = "Integer";
		TestType testTypeString = new TestType();
		testTypeString.storedDataType = "String";

		testOperation.testTypes.add(testTypeInteger);
		testOperation.testTypes.add(testTypeString);

		TestClass testClassHashSet_Int = new TestClass();
		testClassHashSet_Int.testedClass = "HashSet";
		testTypeInteger.testClasses.add(testClassHashSet_Int);

		TestClass testClassHashSet_Str = new TestClass();
		testClassHashSet_Str.testedClass = "HashSet";
		testTypeString.testClasses.add(testClassHashSet_Str);

		TestClass testClassTreeSet_Int = new TestClass();
		testClassTreeSet_Int.testedClass = "TreeSet";
		testTypeInteger.testClasses.add(testClassTreeSet_Int);

		TestClass testClassTreeSet_Str = new TestClass();
		testClassTreeSet_Str.testedClass = "TreeSet";
		testTypeString.testClasses.add(testClassTreeSet_Str);

		for (int i = 1; i <= integerHashSets.size(); i++) {
			System.out.println("Next testing add");
			HashSet<Integer> hint = integerHashSets.get(i - 1);
			double time = test_add_HashSet_Integer(hint, i * STEP_SIZE, CYCLES);
			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= integerTreeSets.size(); i++) {
			System.out.println("Next testing add");
			TreeSet<Integer> tint = integerTreeSets.get(i - 1);
			double time = test_add_TreeSet_Integer(tint, i * STEP_SIZE, CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= stringHashSets.size(); i++) {
			System.out.println("Next testing add");
			HashSet<String> hstr = stringHashSets.get(i - 1);
			double time = test_add_HashSet_String(hstr, i * STEP_SIZE, CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Str.entries.add(entry);

		}

		for (int i = 1; i <= stringTreeSets.size(); i++) {
			System.out.println("Next testing add");
			TreeSet<String> tstr = stringTreeSets.get(i - 1);
			double time = test_add_TreeSet_String(tstr, i * STEP_SIZE, CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Str.entries.add(entry);

		}
		System.out.println("Finished testing add");

	}

	private void testAll_Remove() {

		createFilledSets(STEP_SIZE, MAXIMUM_SIZE, STRING_LENGTH);

		TestOperation testOperation = new TestOperation();
		testOperations.add(testOperation);
		testOperation.testedOperation = "remove";

		TestType testTypeInteger = new TestType();
		testTypeInteger.storedDataType = "Integer";
		TestType testTypeString = new TestType();
		testTypeString.storedDataType = "String";

		testOperation.testTypes.add(testTypeInteger);
		testOperation.testTypes.add(testTypeString);

		TestClass testClassHashSet_Int = new TestClass();
		testClassHashSet_Int.testedClass = "HashSet";
		testTypeInteger.testClasses.add(testClassHashSet_Int);

		TestClass testClassHashSet_Str = new TestClass();
		testClassHashSet_Str.testedClass = "HashSet";
		testTypeString.testClasses.add(testClassHashSet_Str);

		TestClass testClassTreeSet_Int = new TestClass();
		testClassTreeSet_Int.testedClass = "TreeSet";
		testTypeInteger.testClasses.add(testClassTreeSet_Int);

		TestClass testClassTreeSet_Str = new TestClass();
		testClassTreeSet_Str.testedClass = "TreeSet";
		testTypeString.testClasses.add(testClassTreeSet_Str);

		for (int i = 1; i <= integerHashSets.size(); i++) {
			System.out.println("Next testing remove");
			HashSet<Integer> hint = integerHashSets.get(i - 1);
			double time = test_remove_HashSet_Integer(hint, i * STEP_SIZE,
					CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= integerTreeSets.size(); i++) {
			System.out.println("Next testing remove");
			TreeSet<Integer> tint = integerTreeSets.get(i - 1);
			double time = test_remove_TreeSet_Integer(tint, i * STEP_SIZE,
					CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= stringHashSets.size(); i++) {
			System.out.println("Next testing remove");
			HashSet<String> hstr = stringHashSets.get(i - 1);
			double time = test_remove_HashSet_String(hstr, i * STEP_SIZE,
					CYCLES);
			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Str.entries.add(entry);
		}

		for (int i = 1; i <= stringTreeSets.size(); i++) {
			System.out.println("Next testing remove");
			TreeSet<String> tstr = stringTreeSets.get(i - 1);
			double time = test_remove_TreeSet_String(tstr, i * STEP_SIZE,
					CYCLES);
			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Str.entries.add(entry);
		}

	}

	
	private void test_All_Contains(boolean contains) {

		createFilledSets(STEP_SIZE, MAXIMUM_SIZE, STRING_LENGTH);

		TestOperation testOperation = new TestOperation();
		testOperations.add(testOperation);
		if(contains) {
			testOperation.testedOperation = "contains_true";
		} else {
			testOperation.testedOperation = "contains_false";
		}
			

		TestType testTypeInteger = new TestType();
		testTypeInteger.storedDataType = "Integer";
		TestType testTypeString = new TestType();
		testTypeString.storedDataType = "String";

		testOperation.testTypes.add(testTypeInteger);
		testOperation.testTypes.add(testTypeString);

		TestClass testClassHashSet_Int = new TestClass();
		testClassHashSet_Int.testedClass = "HashSet";
		testTypeInteger.testClasses.add(testClassHashSet_Int);

		TestClass testClassHashSet_Str = new TestClass();
		testClassHashSet_Str.testedClass = "HashSet";
		testTypeString.testClasses.add(testClassHashSet_Str);

		TestClass testClassTreeSet_Int = new TestClass();
		testClassTreeSet_Int.testedClass = "TreeSet";
		testTypeInteger.testClasses.add(testClassTreeSet_Int);

		TestClass testClassTreeSet_Str = new TestClass();
		testClassTreeSet_Str.testedClass = "TreeSet";
		testTypeString.testClasses.add(testClassTreeSet_Str);

		for (int i = 1; i <= integerHashSets.size(); i++) {
			System.out.println("Next testing contains");
			HashSet<Integer> hint = integerHashSets.get(i - 1);
			double time = test_contains_integer_hashSet(contains,hint, i * STEP_SIZE,
					CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= integerTreeSets.size(); i++) {
			System.out.println("Next testing contains");
			TreeSet<Integer> tint = integerTreeSets.get(i - 1);
			double time = test_contains_integer_treeSet(contains,tint, i * STEP_SIZE,
					CYCLES);

			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Int.entries.add(entry);
		}

		for (int i = 1; i <= stringHashSets.size(); i++) {
			System.out.println("Next testing contains");
			HashSet<String> hstr = stringHashSets.get(i - 1);
			double time = test_contains_string_hashSet(contains,hstr, i * STEP_SIZE,
					CYCLES);
			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassHashSet_Str.entries.add(entry);
		}

		for (int i = 1; i <= stringTreeSets.size(); i++) {
			System.out.println("Next testing contains");
			TreeSet<String> tstr = stringTreeSets.get(i - 1);
			double time = test_contains_string_treeSet(contains,tstr, i * STEP_SIZE,
					CYCLES);
			Entry entry = new Entry(time, i * STEP_SIZE);
			testClassTreeSet_Str.entries.add(entry);
		}

	}
	
	private void setBounds(int stepSize, int maximumSize) {
		for (int i = stepSize; i <= maximumSize; i = i + stepSize) {
			
		
			HashSet<Integer> newHashSetInteger = new HashSet<Integer>(2 * i);
			TreeSet<Integer> newTreeSetInteger = new TreeSet<Integer>();
			HashSet<String> newHashSetString = new HashSet<String>(2 * i);
			TreeSet<String> newTreeSetString = new TreeSet<String>();

			integerHashSets.add(newHashSetInteger);
			integerTreeSets.add(newTreeSetInteger);
			stringHashSets.add(newHashSetString);
			stringTreeSets.add(newTreeSetString);
		}

	}

	private void createFilledSets(int stepSize, int maximumSize,
			int stringLength) {
		for (int i = 0; i < maximumSize / stepSize; i++) {

			HashSet<Integer> hint = integerHashSets.get(i);
			TreeSet<Integer> tint = integerTreeSets.get(i);
			HashSet<String> hstr = stringHashSets.get(i);
			TreeSet<String> tstr = stringTreeSets.get(i);

			for (int j = 0; j < (i + 1) * stepSize; j++) {
				hint.add(randInts[j]);
				tint.add(randInts[j]);
			}

			for (int j = 0; j < i * stepSize; j++) {
				hstr.add(randomsStrings[j]);
				tstr.add(randomsStrings[j]);
			}
		}
	}

	// fills the hash Set with the Integer numbers and returns the time needed
	private long fill_HashSet_Integer(HashSet<Integer> hint, int amount) {
		final long start = System.nanoTime();
		for (int i = 0; i < amount; i++) {
			hint.add(randInts[i]);
		}
		final long end = System.nanoTime();
		return end - start;
	}

	private long fill_TreeSet_Integer(TreeSet<Integer> tint, int amount) {
		final long start = System.nanoTime();
		for (int i = 0; i < amount; i++) {
			tint.add(randInts[i]);
		}
		final long end = System.nanoTime();
		return end - start;
	}

	private long fill_HashSet_String(HashSet<String> hstr, int amount) {
		final long start = System.nanoTime();
		for (int i = 0; i < amount; i++) {
			hstr.add(randomsStrings[i]);
		}
		final long end = System.nanoTime();
		return end - start;
	}

	private long fill_TreeSet_String(TreeSet<String> tstr, int amount) {
		final long start = System.nanoTime();
		for (int i = 0; i < amount; i++) {
			tstr.add(randomsStrings[i]);
		}
		final long end = System.nanoTime();
		return end - start;
	}

	private double test_add_HashSet_Integer(final HashSet<Integer> hint,
			final int amount, final int cycles) {
		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				hint.add(randInts[i]);
			}
			hint.clear();

		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_add_HashSet_String(final HashSet<String> hstr,
			final int amount, final int cycles) {
		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				hstr.add(randomsStrings[i]);
			}
			hstr.clear();
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_add_TreeSet_Integer(final TreeSet<Integer> tint,
			final int amount, final int cycles) {
		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				tint.add(randInts[i]);
			}
			tint.clear();
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_add_TreeSet_String(final TreeSet<String> tstr,
			final int amount, final int cycles) {

		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				tstr.add(randomsStrings[i]);
			}
			tstr.clear();
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_contains_integer_hashSet(boolean contains,
			final HashSet<Integer> hint, final int amount, final int cycles) {

		int startIndex = 0;
		int endIndex = amount;
		if (!contains) {
			startIndex = amount;
			endIndex = 2 * amount;
		}

		final long startTime = System.nanoTime();
		
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = startIndex; i <endIndex; i++) {
				hint.contains(randInts[i]);
			}
		}
		
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_contains_integer_treeSet(boolean contains,
			final TreeSet<Integer> tint, final int amount, final int cycles) {

		int startIndex = 0;
		int endIndex = amount;
		if (!contains) {
			startIndex = amount;
			endIndex = 2 * amount;
		}

		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = startIndex; i < endIndex; i++) {
				tint.contains(randInts[i]);
			}
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private double test_contains_string_hashSet(boolean contains, final HashSet<String> hstr,
			final int amount, final int cycles) {

		int startIndex = 0;
		int endIndex = amount;
		if (!contains) {
			startIndex = amount;
			endIndex = 2 * amount;
		}

		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = startIndex; i < endIndex; i++) {
				hstr.contains(randomsStrings[i]);
			}
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}
	
	private double test_contains_string_treeSet(boolean contains, final TreeSet<String> tstr,
			final int amount, final int cycles) {

		int startIndex = 0;
		int endIndex = amount;
		if (!contains) {
			startIndex = amount;
			endIndex = 2 * amount;
		}

		final long startTime = System.nanoTime();
		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = startIndex; i < endIndex; i++) {
				tstr.contains(randomsStrings[i]);
			}
		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles));
	}

	private void createRandomIntegers(int amount) {
		randInts = new int[amount];
		Random rand = new Random();
		for (int i = 0; i < randInts.length; i++) {
			int value = rand.nextInt();
			randInts[i] = value;
		}

	}

	private void createRandomStrings(int amount, int stringLength) {
		randomsStrings = new String[amount];
		Random rand = new Random();

		for (int i = 0; i < randomsStrings.length; i++) {
			StringBuilder sb = new StringBuilder();
			for (int j = 0; j < stringLength; j++) {
				sb.append((char) rand.nextInt(Character.MAX_VALUE));
			}
			randomsStrings[i] = sb.toString();
		}
	}

	private double test_remove_HashSet_Integer(final HashSet<Integer> hint,
			final int amount, final int cycles) {

		final long startTime = System.nanoTime();
		long refillingTime = 0;

		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				hint.remove(randInts[i]);
			}

			refillingTime += fill_HashSet_Integer(hint, amount);

		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles))
				- refillingTime / ((double) (amount * cycles));
	}

	private double test_remove_TreeSet_Integer(final TreeSet<Integer> tint,
			final int amount, final int cycles) {

		final long startTime = System.nanoTime();
		long refillingTime = 0;

		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				tint.remove(randInts[i]);
			}

			refillingTime += fill_TreeSet_Integer(tint, amount);

		}
		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles))
				- refillingTime / ((double) (amount * cycles));
	}

	private double test_remove_HashSet_String(final HashSet<String> hstr,
			final int amount, final int cycles) {

		final long startTime = System.nanoTime();
		long refillingTime = 0;

		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				hstr.remove(randomsStrings[i]);
			}

			refillingTime += fill_HashSet_String(hstr, amount);

		}

		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles))
				- refillingTime / ((double) (amount * cycles));
	}

	private double test_remove_TreeSet_String(final TreeSet<String> tstr,
			final int amount, final int cycles) {

		final long startTime = System.nanoTime();
		long refillingTime = 0;

		for (int cycle = 1; cycle <= cycles; cycle++) {
			for (int i = 0; i < amount; i++) {
				tstr.remove(randomsStrings[i]);
			}

			refillingTime += fill_TreeSet_String(tstr, amount);

		}

		final long endTime = System.nanoTime();

		return (endTime - startTime) / ((double) (amount * cycles))
				- refillingTime / ((double) (amount * cycles));
	}

}
